package customview.ike.com.customviewproject.FixBug;

import android.content.Context;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;

import dalvik.system.BaseDexClassLoader;

/**
 *@author:ike
 *@function: bug补丁管理器
 *@creat time:15:54 2018/1/17
 **/

public class FixBugManager {
    private String Tag="FixBugManager";
    /**
     * app安装目录
     */
    private File appDir;
    private Context context;

    public FixBugManager(Context context) {
        this.context = context;
        //在app的安装目录下面创建一个名叫app_fixBugDir的目录（此目录下的所有文件都会被加上app_的前缀）
        this.appDir=context.getDir("odex",Context.MODE_PRIVATE);
        Log.e(Tag,"this.appDir:"+this.appDir.getAbsolutePath());
    }

    /**
     * 修复dex的文件路径
     * @param dexPath
     */
    public void fixDex(String dexPath) throws Exception {
        //判断修复文件是否存在
        File dexSourceFile = new File(dexPath);
        if (!dexSourceFile.exists()){
           Log.e(Tag,"该文件不存在:"+dexPath);
           return;
        }
        //移动(复制)文件到系统的app安装目录下：防止文件被删除的情况发生
        File destFile = new File(appDir, dexSourceFile.getName());
        //检查destFile文件是否已经被复制到app安装目录下，如果是表示该补丁已经被加载了一次，不需要再加载，返回
//        if (destFile.exists()){
//            Log.e(Tag,"补丁已经被加载过了");
//            return;
//        }
        //复制文件
        copyFile(dexSourceFile,destFile);
        //添加补丁
        List<File> fixDexFiles=new ArrayList<>();
        fixDexFiles.add(destFile);
        //开始修复
        startFixDexFiles(fixDexFiles);
    }
    /**
     * 加载补丁文件，开始修复
     * @param fixDexFiles
     */
    private void startFixDexFiles(List<File> fixDexFiles) throws Exception {
        //获取类加载器
        ClassLoader applicationClassLoader = context.getClassLoader();
        //获取应用已经加载完毕的dexElements通过类加载器
        Object applicationDexElements = getElementsByClassLoader(applicationClassLoader);
        File optimizedDirectory=new File(appDir,"optimizedDirectory");
        if (!optimizedDirectory.exists()){
            optimizedDirectory.mkdirs();
        }
        //加载补丁的dex文件集合
        for (File fixDexFile : fixDexFiles) {
            //实例化补丁的类加载器
           ClassLoader baseDexClassLoader = new BaseDexClassLoader(fixDexFile.getAbsolutePath(),
                    optimizedDirectory,
                    null,
                    applicationClassLoader);
           //获取补丁的dex文件
            Object fixElementsByClassLoader = getElementsByClassLoader(baseDexClassLoader);
            //与系统的dex集合合并，将修复的dex几个插在应用的dex集合的前面
            applicationDexElements = combineArray(fixElementsByClassLoader, applicationDexElements);

        }
        //将合并的最新的dex集合反射赋值给系统的dex集合，完成打补丁操作
        injectElements(applicationDexElements);
    }

    /**
     * 将最新的dex集合尽心反射赋值
     * @param applicationDexElements
     */
    private void injectElements(Object applicationDexElements) throws Exception {
        Field pathListFiled = BaseDexClassLoader.class.getDeclaredField("pathList");
        pathListFiled.setAccessible(true);
        Object pathList = pathListFiled.get(context.getClassLoader());
        Field dexElementsFiled = pathList.getClass().getDeclaredField("dexElements");
        dexElementsFiled.setAccessible(true);
        dexElementsFiled.set(pathList,applicationDexElements);
        //////////////////
//        Log.e(Tag,"数组个数=:"+dexElementsFiled.get(pathList).);
    }

    /**
     * 合并两个数组
     *
     * @param arrayLhs
     * @param arrayRhs
     * @return
     */
    private static Object combineArray(Object arrayLhs, Object arrayRhs) {
        Class<?> localClass = arrayLhs.getClass().getComponentType();
        int i = Array.getLength(arrayLhs);
        int j = i + Array.getLength(arrayRhs);
        Object result = Array.newInstance(localClass, j);
        for (int k = 0; k < j; ++k) {
            if (k < i) {
                Array.set(result, k, Array.get(arrayLhs, k));
            } else {
                Array.set(result, k, Array.get(arrayRhs, k - i));
            }
        }
        return result;
    }

    /**
     * 获取系统加载完毕的exElements
     * @param classLoader
     */
    private Object getElementsByClassLoader(ClassLoader classLoader) throws Exception {
        //获取BaseDexClassLoader中的成员变量pathList(保存了所有的系统加载的dex文件)
        Field pathListField = BaseDexClassLoader.class.getDeclaredField("pathList");
        pathListField.setAccessible(true);
        //获取pathList的值
        Object pathListValue = pathListField.get(classLoader);
        //获取pathList的elements数组dexElements
        Field dexElements = pathListValue.getClass().getDeclaredField("dexElements");
        dexElements.setAccessible(true);
        return  dexElements.get(pathListValue);
    }

    /**
     * 复制文件
     *
     * @param src  源文件
     * @param dest 目标文件
     * @throws IOException
     */
    public static void copyFile(File src, File dest) throws IOException {
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            if (!dest.exists()) {
                dest.createNewFile();
            }
            inChannel = new FileInputStream(src).getChannel();
            outChannel = new FileOutputStream(dest).getChannel();
            inChannel.transferTo(0, inChannel.size(), outChannel);
        } finally {
            if (inChannel != null) {
                inChannel.close();
            }
            if (outChannel != null) {
                outChannel.close();
            }
        }
    }
    /**
     * 加载全部的修复包
     */
    public void loadFixDex() throws Exception {
        File[] dexFiles = appDir.listFiles();

        List<File> fixDexFiles = new ArrayList<>();

        for (File dexFile : dexFiles) {
            if (dexFile.getName().endsWith(".dex")) {
                fixDexFiles.add(dexFile);
            }
        }

        startFixDexFiles(fixDexFiles);
    }
}
